/**
 * The Atom robocode robot implements two overall styles to defeat an enemy. 
 * It begins by moving along the edges of the robocode arena while firing in.
 * If along the way it scans a robot near to it, it will attempt to ram it.
 * If either of these techniques appear to be currently unsuccessful, it moves to the
 * center and restarts similar methods.
 * 
 * Credit to Wall.java and Ramfire.java robocode robot 
 * for overall inspiration and some code snippets:
 * 
 * Created by: Matthew A. Nelson
 * Maintained by: Fleming N. Larsen
 * 
 * @author Sean Chung
 */

package chs;

import java.awt.Color;
import robocode.BulletHitEvent;
import robocode.BulletMissedEvent;
import robocode.HitByBulletEvent;
import robocode.HitRobotEvent;
import robocode.Robot;
import robocode.ScannedRobotEvent;

/**
 * The Atom class creates a competitive robocode robot designed for 1 vs. 1 competition.
 * 
 * @author Sean Chung
 *
 */

public class Atom extends Robot {  
  // Keep track of how Atom's doing.
  private int missed;
  private int turnsCharging;
  private int turnsWalling;
  
  // Action variables.
  private boolean charging; 
  
  // Movement values.
  private int turnDirection;
  private double moveAmount;
  
  /**
   * The main method for all robots, must be overriden.
   */
  @Override
  public void run() {
    setColors(Color.RED, Color.BLACK, Color.BLACK, Color.GREEN, Color.YELLOW);    
    startWallHugging();
    turnsWalling = 0;
    
    while (true) {
      if (getCharging()) {
        turnRight (5 * getTurnDirection());
        turnsCharging += 1;
      }   
      // If in "WallHugging" mode, keep traveling along edges.
      else {        
        ahead(getMoveAmount());         
        turnRight(90);
        turnsWalling += 1;
      }
    }
  }

  /**
   * Moves robot along the outer edge of the arena with gun facing in.
   */
  public void startWallHugging() { 
    turnDirection = 1;
    turnsCharging = 0;
    turnsWalling = 0;
    missed = 0;
    charging = false;   
    moveAmount = Math.max(getBattleFieldWidth(), getBattleFieldHeight());
  
    turnLeft(getHeading() % 90);
    turnGunRight(getAbsoluteGun());
    ahead(moveAmount);   
    turnGunRight(90);
    turnRight(90);    
  }

  /**
   * This method is called when robot sees another robot, 
   * i.e. when the robot's radar scan "hits" another robot. 
   * 
   * @param e the scanned robot event set by the game.
   */
  @Override
  public void onScannedRobot(ScannedRobotEvent e) {  
    // Determine if Atom should start ramming or not.
    double healthDifference = e.getEnergy() - getEnergy();
    double distance = e.getDistance();     
    double enemyBearing = e.getBearing();
    close(distance, healthDifference);
    getDecision(enemyBearing, distance);
  }
  
  /**
   * This event is called whenever this robot hits another robot.
   * 
   * @param e the hit robot event set by the game.
   */
  @Override
  public void onHitRobot(HitRobotEvent e) {
    charging = true; 
    turnsCharging -= 1;    
 
    if (e.getBearing() >= 0) {
      turnDirection = 1;
    } 
    else {
      turnDirection = -1;
    }
    
    // Fire on enemy while they're still close, try to chase them down.
    turnRight(e.getBearing());
    turnGunRight(getAbsoluteGun());    
    fire(3.0);
    ahead(40);
  }

  /**
   * This event is called whenever Atoms bullet hits a robot.
   * 
   * @param e the hit bullet event set by the game
   */
  @Override
  public void onBulletHit (BulletHitEvent e) {
    missed -= 1;
    turnsWalling -= 1;
  }
  
  /**
   * This event is called whenever Atoms bullet misses a robot; ie hits wall.
   * 
   * @param e the miss bullet event set by the game
   */
  @Override
  public void onBulletMissed (BulletMissedEvent e) {
    missed += 1;
    turnsWalling += 1;
    // If shots keep missing, go to center
    if (missed == 5) {
      center();
    }
  }
  
  /**
   * This event is called whenever this robot is hit by another robots bullet.
   * 
   * @param e the hit by bullet event set by the game.
   */
  @Override
  public void onHitByBullet (HitByBulletEvent e) {   
    turnGunRight(getAbsoluteGun() + e.getBearing());    
    fire(1.5);
  }
  
  /**
   * Calculates the power to use, based on the distance to enemy.
   * The farther the enemy, the lower the power.
   * 
   * @param distance the distance from targeted enemy
   * @return bullet power to be used
   */
  public double getBulletPower(double distance) {
    double power = 3.0 - ((distance / 100) * 0.5);
    
    if (getCharging()) {
      power = 3.0;
    }
    // Make sure power is at least minimum.
    if (power < 0.1) {
      power = 0.1;
    }    
    // Power should never exceed 3.0 after calculations, just in case.
    // Make sure power does not exceed maximum.
    else if (power > 3.0) {
      power = 3.0;
    }    
    return power;
  }
  
  /**
   * Determines if an enemy robot is close enough and whether Atom 
   * has enough health to ram it safely. If not, do not charge.
   * 
   * @param distance distance in pixels to enemy robot
   * @param healthDifference the difference in energy between Atom and enemy
   * @return true if close and within safe energy differences; false otherwise
   */
  public boolean close(double distance, double healthDifference) {
    boolean close = true;
    if ((distance > 200) && (healthDifference >= 30)) {
      close = false;
      charging = false;
    }
    return close;
  }

  /**
   * Find and move to the center of the battlefield.
   * 
   */
  public void center () {
    double xPosition = getX();
    double yPosition = getY();
    double bearing = getHeading();
    turnsWalling = 0;
    missed = 0;
    
    // Center coordinates are half the fields width and height.
    double xCenter = getBattleFieldWidth() / 2;
    double yCenter = getBattleFieldHeight() / 2;
    
    charging = false;
    
    // Move to center.
    turnRight(heading(bearing));  
    ahead(moveVertical(yPosition, yCenter));
    turnRight(90);
    ahead(moveHorizontal(xPosition, xCenter)); 
    turnGunRight(getAbsoluteGun());
  }

  /** 
   * Finds the heading the robot needs to turn so it is facing
   * perpendicular to the top of the battle field.
   * 
   * @param bearing the robots current heading
   * @return The number of degrees the robot will turn.
   */
  public double heading(double bearing) {
    // If robot is facing to the right, needs to turn left,
    // turning right a negative degree is the same thing.
    if (bearing <= 180) {
      return bearing * -1;
    }
    else {
      return 360 - bearing;
    }          
  }

  /**
   * Finds how far to the left or right the robot 
   * must move to get to the center of the battlefield.
   * 
   * @param xPosition the X coordinate of the robots current position.
   * @param xCenter the Y coordinate of the center of battlefield
   * @return Distance the robot must move horizontally.
   */
  public double moveHorizontal(double xPosition, double xCenter) {
    // If robot's current x coordinate is right of center,
    // needs to move backwards, moving ahead a negative number is the same
    if (xPosition > xCenter) {
      return (xPosition - xCenter) * -1;
    }
    else {
      return xCenter - xPosition;
    }
  }

  /**
   * Finds how far up or down the robot 
   * must move to get to the center of the battlefield.
   * 
   * @param yPosition the Y coordinate of the robots current position
   * @param yCenter the Y coordinate of the center of battlefield
   * @return Distance the robot must move vertically.
   */
  public double moveVertical(double yPosition, double yCenter) {
    // If robot's current x coordinate is right of center,
    // needs to move backwards, moving ahead a negative number is the same
    if (yPosition > yCenter) {
      return (yPosition - yCenter) * -1;
    }
    else {
      return yCenter - yPosition;
    }
  }  
  
  /**
   * Decides the best action for Atom to take this turn.
   * 
   * @param enemyBearing enemy's current heading
   * @param distance enemy's current distance away
   */
  public void getDecision(double enemyBearing, double distance) {
    
    // If Atom hasn't been trying to ram too long, continue trying.
    if (getCharging() && (getTurnsCharging() <= 4)) { 
      if (enemyBearing >= 0) {
        turnDirection = 1;
      } 
      else {
        turnDirection = -1;
      }
      // Turn to enemy's location, turn gun, fire, go towards enemy.
      turnRight(enemyBearing);
      turnGunRight(getAbsoluteGun());
      fire(getBulletPower(distance));      
      ahead(distance + 5);
      
      // May or may not hit robot, keep track of ramming attempts.
      turnsCharging += 1;
    }
    
    // Charging isnt' working, current walling isn't working, go to center.
    else if ((getTurnsCharging() > 3) || (getTurnsWalling() > 4)) {
      center();
    }
    // Must be somewhat stationary, or easy to predict. Fire max, twice.
    else if (missed < -4) {
        fire(3.0);
        fire(3.0);
    } 
    else {
      fire(getBulletPower(distance));
    }
  }
  
  /**
   * Returns the number of bullets that Atom has fired that missed the enemy.
   * If a bullet misses the enemy, increase by 1; decrease by 1 otherwise
   * 
   * @return number of bullets fired by Atom that missed
   */
  public int getMissed() {
    return this.missed;
  }
  
  /**
   * Returns number of turns Atom has been trying to ram the enemy.
   * 
   * @return number of turns Atom has been charging
   */
  public int getTurnsCharging() {
    return this.turnsCharging;
  }
  
  /**
   * Returns the number of turns Atom has been walling.
   * 
   * @return number of turns Atom has been walling
   */
  public int getTurnsWalling() {
    return this.turnsWalling;
  }
  
  /** 
   * Returns wheter Atom is attempting to ram enemy.
   * 
   * @return true if attempting to ram; false otherwise
   */
  public boolean getCharging() {
    return this.charging;
  }
  
  /**
   * Returns direction Atom will turn when charging.
   * 
   * @return -1 if turning a negative angle; 1 otherwise
   */
  public int getTurnDirection() {
    return this.turnDirection;
  }
  
  /**
   * Returns the distance Atom will move every turn when walling.
   * 
   * @return distance in pixels to move
   */
  public double getMoveAmount() {
    return this.moveAmount;
  }
  
  /**
   * Finds what angle the gun needs to turn to be aligned with the current heading.
   * 
   * @return angle to turn
   */
  public double getAbsoluteGun() {
    return getHeading() - getGunHeading();
  }
}